//---------------------------------------------------------------------------
// <copyright file="RibbonComboBox.cs" company="Microsoft Corporation">
//     Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------------

namespace Microsoft.Windows.Controls.Ribbon
{
	#region Using declarations

	using System;
	using System.Collections.Specialized;
	using System.Diagnostics;
	using System.Windows;
	using System.Windows.Automation.Peers;
	using System.Windows.Controls;
	using System.Windows.Controls.Primitives;
	using System.Windows.Documents;
	using System.Windows.Input;
	using System.Windows.Media;
	using System.Windows.Shapes;
	using System.Windows.Threading;
	using Microsoft.Windows.Automation.Peers;
	using Microsoft.Windows.Controls;

	#endregion

	/// <summary>
	///   A ComboBox which can host a RibbonGallery and RibbonMenuItems. 
	///   RibbonComboBox displays selected Text only for first occurrence of a RibbonGallery.
	/// </summary>
	public class RibbonComboBox : RibbonMenuButton
	{
		#region Constructors

		/// <summary>
		///   Initializes static members of the RibbonComboBox class.  Here we override the
		///   default style, a coerce callback, and allow tooltips to be shown for disabled controls.
		/// </summary>
		static RibbonComboBox()
		{
			Type ownerType = typeof(RibbonComboBox);

			DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ownerType));
			IsTextSearchEnabledProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(true));
			InitializeStringContentTemplate();
		}

		private static void InitializeStringContentTemplate()
		{
			DataTemplate template;
			FrameworkElementFactory text;

			// Default template for strings
			template = new DataTemplate();
			text = new FrameworkElementFactory(typeof(TextBlock));
			text.SetValue(TextBlock.TextProperty, new TemplateBindingExtension(ContentPresenter.ContentProperty));
			template.VisualTree = text;
			template.Seal();
			s_StringTemplate = template;
		}

		#endregion

		#region ComboBox Properties

		/// <summary>
		/// DependencyProperty for IsEditable
		/// </summary>
		public static readonly DependencyProperty IsEditableProperty =
				ComboBox.IsEditableProperty.AddOwner(typeof(RibbonComboBox),
				new FrameworkPropertyMetadata(false,
					new PropertyChangedCallback(OnIsEditableChanged)));

		/// <summary>
		///     True if this ComboBox is editable.
		/// </summary>
		/// <value></value>
		public bool IsEditable
		{
			get { return (bool)GetValue(IsEditableProperty); }
			set { SetValue(IsEditableProperty, value); }
		}

		private static void OnIsEditableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			RibbonComboBox cb = d as RibbonComboBox;
			cb.Update();
		}

		/// <summary>
		///     DependencyProperty for Text
		/// </summary>
		public static readonly DependencyProperty TextProperty =
				ComboBox.TextProperty.AddOwner(typeof(RibbonComboBox),
						new FrameworkPropertyMetadata(
								String.Empty,
								FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
								new PropertyChangedCallback(OnTextChanged)));

		/// <summary>
		///     The text of the currently selected item.  When there is no SelectedItem and IsEditable is true
		///     this is the text entered in the text box.  When IsEditable is false, this value represent the string version of the selected item.
		/// </summary>
		/// <value></value>
		public string Text
		{
			get { return (string)GetValue(TextProperty); }
			set { SetValue(TextProperty, value); }
		}

		// When the Text Property changes, search for an item exactly
		// matching the new text and set the selected index to that item
		private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			RibbonComboBox cb = (RibbonComboBox)d;

			RibbonComboBoxAutomationPeer peer = UIElementAutomationPeer.FromElement(cb) as RibbonComboBoxAutomationPeer;
			// Raise the propetyChangeEvent for Value if Automation Peer exist, the new Value must 
			// be the one in SelctionBoxItem(selected value is the one user will care about)
			if (peer != null)
				peer.RaiseValuePropertyChangedEvent((string)e.OldValue, (string)e.NewValue);


			cb.TextUpdated((string)e.NewValue, false);

		}

		/// <summary>
		///     DependencyProperty for the IsReadOnlyProperty
		/// </summary>
		public static readonly DependencyProperty IsReadOnlyProperty =
				TextBox.IsReadOnlyProperty.AddOwner(typeof(RibbonComboBox));

		/// <summary>
		///     When the ComboBox is Editable, if the TextBox within it is read only.
		/// </summary>
		public bool IsReadOnly
		{
			get { return (bool)GetValue(IsReadOnlyProperty); }
			set { SetValue(IsReadOnlyProperty, value); }
		}

		/// <summary>
		///     DependencyProperty for ShowKeyboardCues property.
		/// </summary>
		public static readonly DependencyProperty ShowKeyboardCuesProperty =
			RibbonControlService.ShowKeyboardCuesProperty.AddOwner(typeof(RibbonComboBox));

		/// <summary>
		///     This property is used to decide when to show the Keyboard FocusVisual.
		/// </summary>
		public bool ShowKeyboardCues
		{
			get { return RibbonControlService.GetShowKeyboardCues(this); }
		}

		protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
		{
			ReevalutateFocusVisual();

			// If we're an editable combobox, forward focus to the TextBox element
			if (!e.Handled)
			{
				if (IsEditable && EditableTextBoxSite != null)
				{
					RetainFocusOnEscape = RibbonHelper.IsKeyboardMostRecentInputDevice();

					if (e.OriginalSource == this)
					{
						EditableTextBoxSite.Focus();
						e.Handled = true;
					}
					else if (e.OriginalSource == EditableTextBoxSite)
					{
						EditableTextBoxSite.SelectAll();
					}
				}
			}

			base.OnGotKeyboardFocus(e);
		}

		protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
		{
			base.OnLostKeyboardFocus(e);
			ReevalutateFocusVisual();
		}

		private void ReevalutateFocusVisual()
		{
			bool enable = true;
			if (IsDropDownOpen)
			{
				enable = false;
			}
			else if (!(IsKeyboardFocused ||
				(EditableTextBoxSite != null && EditableTextBoxSite.IsKeyboardFocused) ||
				(PartToggleButton != null && PartToggleButton.IsKeyboardFocused)))
			{
				enable = false;
			}
			if (enable)
			{
				RibbonHelper.EnableFocusVisual(this);
			}
			else
			{
				RibbonHelper.DisableFocusVisual(this);
			}
		}

		/// <summary>
		///     DependencyProperty for TextBoxWidth property.
		/// </summary>
		public static readonly DependencyProperty SelectionBoxWidthProperty =
			DependencyProperty.Register(
					"SelectionBoxWidth",
					typeof(double),
					typeof(RibbonComboBox),
					new FrameworkPropertyMetadata(0.0d));


		/// <summary>
		///   Gets or sets the width of the text box (excluding Image and Label).
		/// </summary>
		public double SelectionBoxWidth
		{
			get { return (double)GetValue(SelectionBoxWidthProperty); }
			set { SetValue(SelectionBoxWidthProperty, value); }
		}


		private static readonly DependencyPropertyKey SelectionBoxItemPropertyKey =
			DependencyProperty.RegisterReadOnly("SelectionBoxItem", typeof(object), typeof(RibbonComboBox),
												new FrameworkPropertyMetadata(String.Empty));

		// This property is used as a Style Helper.
		// When the SelectedItem is a UIElement a VisualBrush is created and set to the Fill property
		// of a Rectangle. Then we set SelectionBoxItem to that rectangle.
		// For data items, SelectionBoxItem is set to a string.
		/// <summary>
		/// The DependencyProperty for the SelectionBoxItemProperty
		/// </summary>
		public static readonly DependencyProperty SelectionBoxItemProperty = SelectionBoxItemPropertyKey.DependencyProperty;

		/// <summary>
		/// Used to display the selected item
		/// </summary>
		public object SelectionBoxItem
		{
			get { return GetValue(SelectionBoxItemProperty); }
			private set { SetValue(SelectionBoxItemPropertyKey, value); }
		}

		private static readonly DependencyPropertyKey SelectionBoxItemTemplatePropertyKey =
			DependencyProperty.RegisterReadOnly("SelectionBoxItemTemplate", typeof(DataTemplate), typeof(RibbonComboBox),
												new FrameworkPropertyMetadata((DataTemplate)null));

		/// <summary>
		/// The DependencyProperty for the SelectionBoxItemTemplate Property
		/// </summary>
		public static readonly DependencyProperty SelectionBoxItemTemplateProperty = SelectionBoxItemTemplatePropertyKey.DependencyProperty;

		/// <summary>
		/// Used to get the item DataTemplate 
		/// </summary>
		public DataTemplate SelectionBoxItemTemplate
		{
			get { return (DataTemplate)GetValue(SelectionBoxItemTemplateProperty); }
			private set { SetValue(SelectionBoxItemTemplatePropertyKey, value); }
		}

		private static readonly DependencyPropertyKey SelectionBoxItemTemplateSelectorPropertyKey =
		   DependencyProperty.RegisterReadOnly("SelectionBoxItemTemplateSelector", typeof(DataTemplateSelector), typeof(RibbonComboBox),
											   new FrameworkPropertyMetadata((DataTemplateSelector)null));

		/// <summary>
		/// The DependencyProperty for the SelectionBoxItemTemplateSelector Property
		/// </summary>
		public static readonly DependencyProperty SelectionBoxItemTemplateSelectorProperty = SelectionBoxItemTemplateSelectorPropertyKey.DependencyProperty;

		/// <summary>
		/// Used to get the ItemTemplateSelector
		/// </summary>
		public DataTemplateSelector SelectionBoxItemTemplateSelector
		{
			get { return (DataTemplateSelector)GetValue(SelectionBoxItemTemplateSelectorProperty); }
			private set { SetValue(SelectionBoxItemTemplateSelectorPropertyKey, value); }
		}

		private static readonly DependencyPropertyKey SelectionBoxItemStringFormatPropertyKey =
			DependencyProperty.RegisterReadOnly("SelectionBoxItemStringFormat", typeof(String), typeof(RibbonComboBox),
												new FrameworkPropertyMetadata((String)null));

		/// <summary>
		/// The DependencyProperty for the SelectionBoxItemProperty
		/// </summary>
		public static readonly DependencyProperty SelectionBoxItemStringFormatProperty = SelectionBoxItemStringFormatPropertyKey.DependencyProperty;

		/// <summary>
		/// Used to set the item DataStringFormat
		/// </summary>
		public String SelectionBoxItemStringFormat
		{
			get { return (String)GetValue(SelectionBoxItemStringFormatProperty); }
			private set { SetValue(SelectionBoxItemStringFormatPropertyKey, value); }
		}

		/// <summary>
		///     DependencyProperty for StaysOpenOnEdit
		/// </summary>
		public static readonly DependencyProperty StaysOpenOnEditProperty
			= ComboBox.StaysOpenOnEditProperty.AddOwner(typeof(RibbonComboBox),
										  new FrameworkPropertyMetadata(false));

		/// <summary>
		///     Determines whether the ComboBox will remain open when clicking on
		///     the text box when the drop down is open
		/// </summary>
		/// <value></value>
		public bool StaysOpenOnEdit
		{
			get
			{
				return (bool)GetValue(StaysOpenOnEditProperty);
			}
			set
			{
				SetValue(StaysOpenOnEditProperty, value);
			}
		}

		#endregion

		#region Selector Properties

		/// <summary>
		///  The first item in the current selection, or null if the selection is empty.
		/// </summary>
		private object SelectedItem
		{
			get { return _selectedItem; }
			set
			{
				if (_firstGallery != null && !UpdatingSelectedItem)
				{
					_firstGallery.SelectedItem = value;
				}
				_selectedItem = value;
			}
		}

		private object HighlightedItem
		{
			get
			{
				if (_firstGallery != null)
				{
					return _firstGallery.HighlightedItem;
				}
				return null;
			}

			set
			{
				if (_firstGallery != null)
				{
					_firstGallery.HighlightedItem = value;
				}
			}
		}

		// When the selected item (or its content) changes, update
		// The SelectedItem property and the Text properties
		// ComboBoxItem also calls this method when its content changes
		private void SelectedItemUpdated()
		{
			try
			{
				UpdatingSelectedItem = true;

				// If the selection changed as a result of Text or the TextBox
				// changing, don't update the Text property - TextUpdated() will
				if (!UpdatingText)
				{
					string text = String.Empty;
					if (_firstGallery != null && _firstGallery.SelectedCategory != null)
					{
						text = TextSearchInternal.GetPrimaryTextFromItem(_firstGallery.SelectedCategory, SelectedItem, true);
						_firstGallery.ScrollIntoView(SelectedItem);
					}

					// if _firstGallery.SelectedCategory is null, it means SelectedItem is null and we should set Text to empty string.
					if (Text != text)
					{
						SetValue(TextProperty, text);
					}
				}

				// Update SelectionItem/TextBox
				Update();
			}
			finally
			{
				UpdatingSelectedItem = false;
			}
		}

		// When the highlighted item changes, update text properties
		private void HighlightedItemUpdated()
		{
			try
			{
				UpdatingHighlightedItem = true;

				// If the highlight changed as a result of Text or the TextBox
				// changing, don't update the Text property - TextUpdated() will
				if (!UpdatingText)
				{
					string text = String.Empty;
					if (_firstGallery != null && _firstGallery.HighlightedCategory != null)
					{
						text = TextSearchInternal.GetPrimaryTextFromItem(_firstGallery.HighlightedCategory, HighlightedItem, true);
						_firstGallery.ScrollIntoView(HighlightedItem);
					}

					// if _firstGallery.HighlightedCategory is null, it means HighlightedItem is null and we should set Text to empty string.
					if (Text != text)
					{
						SetValue(TextProperty, text);
					}
				}

				// Update SelectionItem/TextBox
				Update();
			}
			finally
			{
				UpdatingHighlightedItem = false;
			}
		}

		// Updates:
		//    SelectionBox if not editable
		//    EditableTextBox.Text if editable
		private void Update()
		{
			if (IsEditable)
			{
				UpdateEditableTextBox();
			}
			else
			{
				UpdateSelectionBoxItem();
			}
		}

		// Update the editable TextBox to match combobox text
		private void UpdateEditableTextBox()
		{
			if (!UpdatingText)
			{
				try
				{
					UpdatingText = true;

					string text = Text;

					// Copy ComboBox.Text to the editable TextBox
					if (EditableTextBoxSite != null && EditableTextBoxSite.Text != text)
					{
						EditableTextBoxSite.Text = text;
						EditableTextBoxSite.SelectAll();
					}
				}
				finally
				{
					UpdatingText = false;
				}
			}
		}

		/// <summary>
		/// This function updates the selected item in the "selection box".
		/// This is called when selection changes or when the combobox
		/// switches from editable to non-editable or vice versa.
		/// This will also get called in ApplyTemplate in case selection
		/// is set prior to the control being measured.
		/// </summary>
		private void UpdateSelectionBoxItem()
		{
			// propagate the new selected item to the SelectionBoxItem property;
			// this displays it in the selection box
			object item = SelectedItem;
			DataTemplate itemTemplate = null;
			string stringFormat = null;
			DataTemplateSelector itemTemplateSelector = null;

			if (_firstGallery != null)
			{
				RibbonGalleryCategory selectedCategory = _firstGallery.SelectedCategory;
				if (selectedCategory != null)
				{
					itemTemplate = selectedCategory.ItemTemplate;
					stringFormat = selectedCategory.ItemStringFormat;
					itemTemplateSelector = selectedCategory.ItemTemplateSelector;
				}
			}

			// if Items contains an explicit ContentControl, use its content instead
			// (this handles the case of ComboBoxItem)
			ContentControl contentControl = item as ContentControl;

			if (contentControl != null)
			{
				item = contentControl.Content;
				itemTemplate = contentControl.ContentTemplate;
				stringFormat = contentControl.ContentStringFormat;
			}

			if (_clonedElement != null)
			{
				_clonedElement.LayoutUpdated -= CloneLayoutUpdated;
				_clonedElement = null;
			}

			if (itemTemplate == null && itemTemplateSelector == null && stringFormat == null)
			{
				// if the item is a logical element it cannot be displayed directly in
				// the selection box because it already belongs to the tree (in the dropdown box).
				// Instead, try to extract some useful text from the visual.
				DependencyObject logicalElement = item as DependencyObject;

				if (logicalElement != null)
				{
					// If the item is a UIElement, create a copy using a visual brush
					_clonedElement = logicalElement as UIElement;

					if (_clonedElement != null)
					{
						// Create visual copy of selected element
						VisualBrush visualBrush = new VisualBrush(_clonedElement);
						visualBrush.Stretch = Stretch.None;

						//Set position and dimension of content
						visualBrush.ViewboxUnits = BrushMappingMode.Absolute;
						visualBrush.Viewbox = new Rect(_clonedElement.RenderSize);

						//Set position and dimension of tile
						visualBrush.ViewportUnits = BrushMappingMode.Absolute;
						visualBrush.Viewport = new Rect(_clonedElement.RenderSize);

						// If the FlowDirection on cloned element doesn't match the combobox's apply a mirror
						// If the FlowDirection on cloned element doesn't match its parent's apply a mirror
						// If both are true, they cancel out so no mirror should be applied
						FlowDirection elementFD = (FlowDirection)_clonedElement.GetValue(FlowDirectionProperty);
						DependencyObject parent = VisualTreeHelper.GetParent(_clonedElement);
						FlowDirection parentFD = parent == null ? FlowDirection : (FlowDirection)parent.GetValue(FlowDirectionProperty);
						if ((elementFD != this.FlowDirection) != (elementFD != parentFD))
						{
							visualBrush.Transform = new MatrixTransform(new Matrix(-1.0, 0.0, 0.0, 1.0, _clonedElement.RenderSize.Width, 0.0));
						}

						// Apply visual brush to a rectangle
						Rectangle rect = new Rectangle();
						rect.Fill = visualBrush;
						rect.Width = _clonedElement.RenderSize.Width;
						rect.Height = _clonedElement.RenderSize.Height;

						_clonedElement.LayoutUpdated += CloneLayoutUpdated;

						item = rect;
						itemTemplate = null;
					}
					else
					{
						item = ExtractString(logicalElement);
						itemTemplate = StringContentTemplate;
					}
				}
			}

			// display a null item by an empty string
			if (item == null)
			{
				item = String.Empty;
				itemTemplate = StringContentTemplate;
			}

			SelectionBoxItem = item;
			SelectionBoxItemTemplate = itemTemplate;
			SelectionBoxItemTemplateSelector = itemTemplateSelector;
			SelectionBoxItemStringFormat = stringFormat;
		}

		// Update our clone's size to match the actual object's size
		private void CloneLayoutUpdated(object sender, EventArgs e)
		{
			Rectangle rect = (Rectangle)SelectionBoxItem;
			rect.Width = _clonedElement.RenderSize.Width;
			rect.Height = _clonedElement.RenderSize.Height;

			VisualBrush visualBrush = (VisualBrush)rect.Fill;
			visualBrush.Viewbox = new Rect(_clonedElement.RenderSize);
			visualBrush.Viewport = new Rect(_clonedElement.RenderSize);
		}

		// When the user types in the TextBox, search for an item that partially
		// matches the new text and set the selected index to that item
		private void OnEditableTextBoxTextChanged(object sender, TextChangedEventArgs e)
		{
			Debug.Assert(_editableTextBoxSite == sender);

			if (!IsEditable)
			{
				// Don't do any work if we're not editable.
				return;
			}

			TextUpdated(EditableTextBoxSite.Text, true);
		}

		// When selection changes, save the location of the selection start
		private void OnEditableTextBoxSelectionChanged(object sender, RoutedEventArgs e)
		{
			_textBoxSelectionStart = EditableTextBoxSite.SelectionStart;
		}

		// If TextSearch is enabled search for an item matching the new text
		// (partial search if user is typing, exact search if setting Text)
		private void TextUpdated(string newText, bool textBoxUpdated)
		{
			// Only process this event if it is coming from someone outside setting Text directly
			if (!UpdatingText && !UpdatingSelectedItem && !UpdatingHighlightedItem)
			{
				try
				{
					// Set the updating flags so we don't reenter this function
					UpdatingText = true;

					// Try searching for an item matching the new text
					if (IsTextSearchEnabled && _firstGallery != null)
					{
						ItemsControl matchedGalleryCategory = null;
						object matchedItem = TextSearchInternal.FindMatchingPrefix(_firstGallery, newText, true, out matchedGalleryCategory);

						if (matchedItem != null)
						{
							// Allow partial matches when updating textbox
							if (textBoxUpdated)
							{
								int selectionStart = EditableTextBoxSite.SelectionStart;
								// Perform type search when the selection is at the end
								// of the textbox and the selection start increased
								if (selectionStart == newText.Length &&
									selectionStart > _textBoxSelectionStart)
								{
									// Replace the currently typed text with the text
									// from the matched item
									string matchedText = TextSearchInternal.GetPrimaryTextFromItem(matchedGalleryCategory, matchedItem, true);

									//Replace the TextBox's text with the matched text and
									//select the text beyond what the user typed
									EditableTextBoxSite.Text = matchedText;
									EditableTextBoxSite.SelectionStart = newText.Length;
									EditableTextBoxSite.SelectionLength = matchedText.Length - newText.Length;

									// ComboBox's text property should be updated with the matched text
									newText = matchedText;
								}
							}
							else //Text Property Set
							{
								//Require exact matches when setting TextProperty
								string matchedText = TextSearchInternal.GetPrimaryTextFromItem(matchedGalleryCategory, matchedItem, true);
								if (!String.Equals(newText, matchedText, StringComparison.CurrentCulture))
								{
									// Strings not identical, no match
									matchedItem = null;
								}
							}

							if (matchedItem != _firstGallery.HighlightedItem)
							{
								// Cache the previously selected item for this session.

								CacheSelectedItem();

								try
								{
									// Highlight the newly matched item

									_firstGallery.ShouldExecuteCommand = IsDropDownOpen;

									bool updated = false;

									if (matchedGalleryCategory != null && matchedItem != null)
									{
										RibbonGalleryItem galleryItem =
											matchedGalleryCategory.ItemContainerGenerator.ContainerFromItem(matchedItem) as RibbonGalleryItem;
										if (galleryItem != null)
										{
											updated = true;
											galleryItem.IsHighlighted = true;
										}
									}

									if (!updated)
									{
										_firstGallery.HighlightedItem = matchedItem;
									}
								}
								finally
								{
									_firstGallery.ShouldExecuteCommand = true;
								}

								// Scroll the newly matched item into view

								_firstGallery.ScrollIntoView(matchedItem);
							}
						}
					}


					// Update TextProperty when TextBox changes and TextBox when TextProperty changes
					if (textBoxUpdated)
					{
						SetValue(TextProperty, newText);
					}
					else if (EditableTextBoxSite != null)
					{
						EditableTextBoxSite.Text = newText;
					}
				}
				finally
				{
					// Clear the updating flag
					UpdatingText = false;
				}
			}
		}

		private static string ExtractString(DependencyObject d)
		{
			TextBlock text;
			Visual visual;
			TextElement textElement;
			string strValue = String.Empty;

			if ((text = d as TextBlock) != null)
			{
				strValue = text.Text;
			}
			else if ((visual = d as Visual) != null)
			{
				int count = VisualTreeHelper.GetChildrenCount(visual);
				for (int i = 0; i < count; i++)
				{
					strValue += ExtractString((DependencyObject)(VisualTreeHelper.GetChild(visual, i)));
				}
			}
			else if ((textElement = d as TextElement) != null)
			{
				strValue += textElement.ContentStart.GetTextInRun(LogicalDirection.Forward);
			}

			return strValue;
		}

		internal override void OnIsDropDownOpenChanged(DependencyPropertyChangedEventArgs e)
		{
			base.OnIsDropDownOpenChanged(e);

			ReevalutateFocusVisual();
			if ((bool)e.NewValue)
			{
				// Select text if editable
				if (IsEditable && EditableTextBoxSite != null)
				{
					EditableTextBoxSite.Focus();
					EditableTextBoxSite.SelectAll();
				}

				// Cache the previously selected item for this session
				CacheSelectedItem();

				Dispatcher.BeginInvoke((Action)delegate()
				{
					if (_firstGallery != null)
					{
						// Scroll the highlighted item into view. Note that we need to do the 
						// scroll in a Dispatcher operation because the scroll operation wont 
						// succeed until the Popup contents are Loaded and connected to a 
						// PresentationSource. We need to allow time for that to happen.

						_firstGallery.ScrollIntoView(_firstGallery.HighlightedItem);
					}
				},
				DispatcherPriority.Render);
			}
		}

		private object SelectedValue
		{
			get { return _selectedValue; }
			set { _selectedValue = value; }
		}

		private string SelectedValuePath
		{
			get { return _selectedValuePath; }
			set { _selectedValuePath = value; }
		}

		#endregion

		#region Protected override Methods

		protected override AutomationPeer OnCreateAutomationPeer()
		{
			return new RibbonComboBoxAutomationPeer(this);
		}

		protected override void OnKeyDown(KeyEventArgs e)
		{
			if (e.Handled)
			{
				return;
			}

			bool handled = false;
			if ((e.Key == Key.Escape) &&
				IsSelectedItemCached &&
				(IsDropDownOpen || IsEditable))
			{
				// Cancel changes of escape
				if (IsEditable && !IsDropDownOpen)
				{
					handled = true;
				}
				CommitOrCancelChanges(false /* cancelChanges */);
			}

			if ((e.Key == Key.F4) &&
				IsSelectedItemCached &&
				IsDropDownOpen)
			{
				// Cancel changes on F4
				CommitOrCancelChanges(false /* cancelChanges */);
			}

			UIElement targetFocusOnFalse = null;
			if (IsEditable)
			{
				targetFocusOnFalse = EditableTextBoxSite;
			}
			else if (RetainFocusOnEscape)
			{
				targetFocusOnFalse = PartToggleButton;
			}
			RibbonHelper.HandleDropDownKeyDown(this,
				e,
				delegate() { return IsDropDownOpen; },
				delegate(bool value) { IsDropDownOpen = value; },
				targetFocusOnFalse,
				Popup.TryGetChild());

			if (e.Handled)
			{
				return;
			}

			Key key = e.Key;
			if (key == Key.System)
			{
				key = e.SystemKey;
			}

			switch (key)
			{
				case Key.Enter:
				case Key.F10:
					if (IsEditable || IsDropDownOpen)
					{
						// Commit changes on Enter or F10.
						CommitOrCancelChanges(true /* commitChanges */);

						// Dismiss parent Popups
						RaiseEvent(new RibbonDismissPopupEventArgs());

						handled = true;
					}
					break;
			}

			if (handled)
			{
				e.Handled = true;
			}

			base.OnKeyDown(e);
		}

		protected override void OnPreviewKeyDown(KeyEventArgs e)
		{
			// Handles the navigation scenarios for the first gallery.
			if (NavigateInFirstGallery)
			{
				bool handled = false;
				Key key = e.Key;
				if (FlowDirection == FlowDirection.RightToLeft)
				{
					// In Right to Left mode we switch Right and Left keys
					if (key == Key.Left)
					{
						key = Key.Right;
					}
					else if (key == Key.Right)
					{
						key = Key.Left;
					}
				}

				// Note that Keyboard focus is never on the first gallery (except for its filter
				// area). In any other region of first gallery gets focus it is assumed that the
				// focus is delegate back to non-popup parts of combobox. Hence here we handle only
				// the cases where focus is in non-popup parts of combobox. For other regions we assume
				// that their default keyboard navigation will kick in.
				RibbonGalleryItem focusedGalleryItem = null;
				if (IsDropDownOpen && _firstGallery != null)
				{
					focusedGalleryItem = _firstGallery.HighlightedContainer;
					if (focusedGalleryItem == null && _firstGallery.SelectedContainers.Count > 0)
					{
						focusedGalleryItem = _firstGallery.SelectedContainers[0];
					}
				}

				switch (key)
				{
					case Key.Tab:
						if (IsDropDownOpen)
						{
							bool shiftPressed = ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
							if (focusedGalleryItem == null)
							{
								// Move focus to the beggining or the end of popup.
								if (shiftPressed)
								{
									handled = RibbonHelper.NavigateToPreviousMenuItemOrGallery(this, Items.Count, BringIndexIntoView);
								}
								else
								{
									handled = RibbonHelper.NavigateToNextMenuItemOrGallery(this, -1, BringIndexIntoView);
								}
							}
							if (!handled && focusedGalleryItem != null)
							{
								// Move focus to the next/previous element.
								focusedGalleryItem.MoveFocus(new TraversalRequest(shiftPressed ? FocusNavigationDirection.Previous : FocusNavigationDirection.Next));
								handled = true;
							}
						}
						break;
					case Key.Up:
						if (IsDropDownOpen)
						{
							if (focusedGalleryItem == null)
							{
								// Navigate to the bottom of the popup.
								handled = RibbonHelper.NavigateToPreviousMenuItemOrGallery(this, Items.Count, BringIndexIntoView);
							}
							if (!handled)
							{
								// Navigate and highlight the gallery item above the current.
								handled = RibbonHelper.NavigateAndHighlightGalleryItem(focusedGalleryItem, FocusNavigationDirection.Up);
							}
							if (!handled)
							{
								// Navigate outside the first gallery.
								_firstGallery.OnNavigationKeyDown(e, focusedGalleryItem);
							}
							handled = true;
						}
						break;

					case Key.Down:
						{
							if (!IsDropDownOpen && IsEditable)
							{
								// Open drop down
								IsDropDownOpen = true;
								handled = true;
							}
							else if (IsDropDownOpen)
							{
								if (focusedGalleryItem == null)
								{
									// Navigate to the top of the popup.
									handled = RibbonHelper.NavigateToNextMenuItemOrGallery(this, -1, BringIndexIntoView);
								}
								if (!handled)
								{
									// Navigate and highlight the gallery item below the current.
									handled = RibbonHelper.NavigateAndHighlightGalleryItem(focusedGalleryItem, FocusNavigationDirection.Down);
								}
								if (!handled)
								{
									// Navigate outside the gallery.
									_firstGallery.OnNavigationKeyDown(e, focusedGalleryItem);
								}
								handled = true;
							}
						}
						break;

					case Key.Right:
						if (IsDropDownOpen)
						{
							if (!IsEditable)
							{
								// Navigate and highlight the gallery item to the right.
								RibbonHelper.NavigateAndHighlightGalleryItem(focusedGalleryItem, FocusNavigationDirection.Right);
								handled = true;
							}
						}
						break;

					case Key.Left:
						if (IsDropDownOpen)
						{
							if (!IsEditable)
							{
								// Navigate and highlight the gallery item to the left.
								RibbonHelper.NavigateAndHighlightGalleryItem(focusedGalleryItem, FocusNavigationDirection.Left);
								handled = true;
							}
						}
						break;

					case Key.PageUp:
						if (IsDropDownOpen)
						{
							RibbonHelper.NavigatePageAndHighlightRibbonGalleryItem(_firstGallery, focusedGalleryItem, FocusNavigationDirection.Up);
							handled = true;
						}
						break;

					case Key.PageDown:
						if (IsDropDownOpen)
						{
							RibbonHelper.NavigatePageAndHighlightRibbonGalleryItem(_firstGallery, focusedGalleryItem, FocusNavigationDirection.Down);
							handled = true;
						}
						break;
				}

				if (handled)
				{
					e.Handled = true;
					return;
				}
			}
			else
			{
				base.OnPreviewKeyDown(e);
			}
		}

		private bool NavigateInFirstGallery
		{
			get
			{
				// Note that the _firstGallery does not physically take keyboard focus.
				// Hence if either the combobox itself or its editable textbox or its
				// toggle button has focus, then we assume navigation for first gallery.
				return _firstGallery != null &&
					(this.IsKeyboardFocused ||
					(EditableTextBoxSite != null && EditableTextBoxSite.IsKeyboardFocused) ||
					(PartToggleButton != null && PartToggleButton.IsKeyboardFocused));
			}
		}

		private bool IsFocusWithinFirstGalleryItemsHostSite
		{
			get
			{
				if (_firstGallery != null &&
					_firstGallery.IsKeyboardFocusWithin)
				{
					Panel galleryItemsHostSite = _firstGallery.ItemsHostSite;
					if (galleryItemsHostSite != null)
					{
						return galleryItemsHostSite.IsKeyboardFocusWithin;
					}

					DependencyObject focusedElement = Keyboard.FocusedElement as DependencyObject;
					if (focusedElement != null &&
						(TreeHelper.FindVisualAncestor<RibbonGalleryCategory>(focusedElement) != null))
					{
						return true;
					}
				}
				return false;
			}
		}

		protected override void OnKeyUp(KeyEventArgs e)
		{
			if (e.OriginalSource == this ||
				e.OriginalSource == PartToggleButton)
			{
				if (e.Key == Key.Space ||
					e.Key == Key.Enter)
				{
					IsDropDownOpen = true;
					e.Handled = true;
				}
			}

			if (!e.Handled)
			{
				base.OnKeyUp(e);
			}
		}

		protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
		{
			if ((bool)e.OldValue)
			{
				// Whenever focus is moving outside the ComboBox we attempt 
				// to commit pending changes. Please note that the ESC key 
				// is special and that we would have already cancelled the 
				// pending changes during the PreviewKeyDown listener in that 
				// case and the current attempt to commit the changes will 
				// no-op. For all other cases though we will commit pending 
				// changes.
				Dispatcher.BeginInvoke(
					(Action)delegate()
					{
						if (!IsKeyboardFocusWithin)
						{
							CommitOrCancelChanges(true /* commitChanges */);
						}
					},
					DispatcherPriority.Input,
					null);
			}

			base.OnIsKeyboardFocusWithinChanged(e);
		}

		protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
		{
			if (IsEditable)
			{
				Visual originalSource = e.OriginalSource as Visual;
				Visual textBox = EditableTextBoxSite;

				if (originalSource != null && textBox != null
					&& textBox.IsAncestorOf(originalSource))
				{
					if (IsDropDownOpen && StaysOpenOnEdit)
					{
						// clicking the text box should not close the combobox.
						// so return without calling base.OnPreviewMouseDown.
						// dont mark e.Handled because TextBox needs to recieve MouseDown
						return;
					}
					else if (!IsKeyboardFocusWithin)
					{
						// If textBox is clicked, claim focus
						Focus();
						e.Handled = true;   // Handle so that textbox won't try to update cursor position
					}
				}
			}
			base.OnPreviewMouseDown(e);
		}

		protected override void OnTextInput(TextCompositionEventArgs e)
		{
			// Only handle text from ourselves or a GalleryItem
			if (!String.IsNullOrEmpty(e.Text) && IsTextSearchEnabled && NavigateInFirstGallery)
			{
				TextSearchInternal instance = TextSearchInternal.EnsureInstance(_firstGallery);

				if (instance != null)
				{
					instance.DoHierarchicalSearch(e.Text);
					// Note: we always want to handle the event to denote that we
					// actually did something.  We wouldn't want an AccessKey
					// to get invoked just because there wasn't a match here.
					e.Handled = true;
				}
			}

			// Dont call base as we dont want to have TextSearch on Items other than _firstGallery
			// base.OnTextInput(e);
		}

		public override void OnApplyTemplate()
		{
			CoerceValue(ControlSizeDefinitionProperty);
			base.OnApplyTemplate();

			EditableTextBoxSite = GetTemplateChild(EditableTextBoxTemplateName) as TextBox;

			// EditableTextBoxSite should have been set by now if it's in the visual tree
			if (EditableTextBoxSite != null)
			{
				EditableTextBoxSite.TextChanged += new TextChangedEventHandler(OnEditableTextBoxTextChanged);
				EditableTextBoxSite.SelectionChanged += new RoutedEventHandler(OnEditableTextBoxSelectionChanged);
			}

			// At startup ComboBox needs SelectedItem from its first RibbonGallery in order to populate its TextBox.
			// RibbonGallery needs to be hooked up to the Visual tree, so that its DataContext is inherited.
			// Similarly RibbonGalleryCategory and RibbonGalleryItem should be in Visual tree to extract SelectedItem and the container for the SelectedItem
			// Therefore its not sufficient to just generate containers, we need a full Layout on RibbonGallery.
			if (Popup != null && Popup.Child != null)
			{
				Popup.Child.Measure(new Size());
				UpdateFirstGallery();
			}
		}

		protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
		{
			base.OnItemsChanged(e);
			UpdateFirstGallery();
		}

		/// <summary>
		/// Cache Container and item of first occurrence of a RibbonGallery
		/// </summary>
		private void UpdateFirstGallery()
		{
			_firstGalleryItem = null;
			RibbonGallery firstGallery = null;

			foreach (object item in Items)
			{
				firstGallery = ItemContainerGenerator.ContainerFromItem(item) as RibbonGallery;
				if (firstGallery != null)
				{
					_firstGalleryItem = new WeakReference(item);
					break;
				}
			}

			FirstGallery = firstGallery;
		}

		protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
		{
			// If a new Gallery container has been generated for _galleryItem, update _gallery reference.
			RibbonGallery gallery = element as RibbonGallery;
			if (gallery != null)
			{
				if (_firstGalleryItem != null && _firstGalleryItem.IsAlive && _firstGalleryItem.Target.Equals(item))
				{
					FirstGallery = gallery;
				}
			}

			base.PrepareContainerForItemOverride(element, item);
		}

		protected override void ClearContainerForItemOverride(DependencyObject element, object item)
		{
			if (_firstGalleryItem != null && _firstGalleryItem.IsAlive && _firstGalleryItem.Target.Equals(item))
			{
				FirstGallery = null;
			}
			base.ClearContainerForItemOverride(element, item);
		}

		private void UpdateSelectionProperties()
		{
			try
			{
				UpdatingSelectedItem = true;

				// Update selected* properties, SelectedItem could become null if there is no gallery.
				object selectedItem = null, selectedValue = null;
				string selectedValuePath = string.Empty;

				if (_firstGallery != null)
				{
					selectedItem = _firstGallery.SelectedItem;
					selectedValue = _firstGallery.SelectedValue;
					selectedValuePath = _firstGallery.SelectedValuePath;
				}

				SelectedItem = selectedItem;
				SelectedValue = selectedValue;
				SelectedValuePath = selectedValuePath;

				// Update editableTextBox Text with the selectedItem
				SelectedItemUpdated();
			}
			finally
			{
				UpdatingSelectedItem = false;
			}
		}

		void OnGallerySelectionChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
		{
			UpdateSelectionProperties();
		}

		void OnGalleryItemSelectionChanged(object sender, RoutedEventArgs e)
		{
			UpdateSelectionProperties();
		}

		void OnGalleryHighlightChanged(object sender, EventArgs e)
		{
			// Note that the _firstGallery does not physically take keyboard focus.

			// Note that IsKeyboardMostRecentInputDevice results in false positives because 
			// InputManager.Current.MostRecentInputDevice is only updated upon MouseDown/Up 
			// event not during a MouseMove. Thus we end up processing the HighlightItem 
			// changes caused via a MouseMove after a key navigation. Hence the additional 
			// logic to detect this scenario in RibbonGallery.

			//if (RibbonHelper.IsKeyboardMostRecentInputDevice() && navigateInFirstGallery)
			if (_firstGallery != null &&
				!_firstGallery.HasHighlightChangedViaMouse &&
				(NavigateInFirstGallery || IsFocusWithinFirstGalleryItemsHostSite))
			{
				HighlightedItemUpdated();
			}
		}

		void OnGalleryGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
		{
			// When on of the GalleryItems within the _firstGallery acquires Keyboard 
			// focus reinstate focus to the parent based on the IsEditable mode

			RibbonGalleryItem focusedGalleryItem = Keyboard.FocusedElement as RibbonGalleryItem;
			if (focusedGalleryItem != null)
			{
				if (IsEditable && EditableTextBoxSite != null)
				{
					EditableTextBoxSite.Focus();
				}
				else if (!IsEditable && PartToggleButton != null)
				{
					PartToggleButton.Focus();
				}
				else
				{
					Focus();
				}
			}
		}

		internal override void TransferPseudoInheritedProperties()
		{
			//base.TransferPseudoInheritedProperties();
		}

		#endregion

		#region Input

		private void CommitOrCancelChanges(bool commitChanges)
		{
			// Close the dropdown and commit the selection if requested.
			if (commitChanges)
			{
				DiscardCachedSelectedItem();
			}
			else
			{
				RestoreCachedSelectedItem();
			}

			SelectedItemUpdated();
		}

		private void CacheSelectedItem()
		{
			if (_firstGallery != null && !IsSelectedItemCached)
			{
				try
				{
					// Temporarily clear Gallery's selection
					_firstGallery.ShouldExecuteCommand = false;
					IsSelectedItemCached = true;
					_cachedSelectedItem = _firstGallery.SelectedItem;
					_firstGallery.SelectedItem = null;
				}
				finally
				{
					_firstGallery.ShouldExecuteCommand = true;
				}

				_firstGallery.HighlightedItem = _cachedSelectedItem;
			}
		}

		private void RestoreCachedSelectedItem()
		{
			if (_firstGallery != null && IsSelectedItemCached)
			{
				try
				{
					// Restore Gallery's selection

					_firstGallery.ShouldExecuteCommand = false;
					SelectedItem = _cachedSelectedItem;
					_cachedSelectedItem = null;
					IsSelectedItemCached = false;
				}
				finally
				{
					_firstGallery.ShouldExecuteCommand = true;
				}

				HighlightedItem = null;
			}
		}

		private void DiscardCachedSelectedItem()
		{
			if (IsSelectedItemCached)
			{
				// Discard cached selection and set selection 
				// to be the same as highlight

				if (HighlightedItem != null)
				{
					SelectedItem = HighlightedItem;
					HighlightedItem = null;
				}
				else
				{
					SelectedItem = _cachedSelectedItem;
				}
				_cachedSelectedItem = null;
				IsSelectedItemCached = false;
			}
		}

		#endregion

		#region KeyTips

		protected override void OnActivatingKeyTip(ActivatingKeyTipEventArgs e)
		{
			if (e.OriginalSource == this)
			{
				RibbonHelper.SetKeyTipPlacementForTextBox(this, e, EditableTextBoxSite);
			}
		}

		protected override void OnKeyTipAccessed(KeyTipAccessedEventArgs e)
		{
			if (e.OriginalSource == this)
			{
				if (this.IsEditable)
				{
					if (EditableTextBoxSite != null)
					{
						RibbonHelper.OpenParentRibbonGroupDropDownSync(this, TemplateApplied);
						EditableTextBoxSite.Focus();
						EditableTextBoxSite.SelectAll();
					}
					e.Handled = true;
				}
				else
				{
					base.OnKeyTipAccessed(e);
				}
			}
			else
			{
				base.OnKeyTipAccessed(e);
			}
		}

		#endregion

		#region Private Properties

		private RibbonGallery FirstGallery
		{
			get { return _firstGallery; }
			set
			{
				if (_firstGallery != null)
				{
					_firstGallery.ShouldGalleryItemsAcquireFocus = true;

					_firstGallery.HighlightChanged -= new EventHandler(OnGalleryHighlightChanged);
					_firstGallery.SelectionChanged -= new RoutedPropertyChangedEventHandler<object>(OnGallerySelectionChanged);
					_firstGallery.RemoveHandler(RibbonGalleryItem.SelectedEvent, new RoutedEventHandler(OnGalleryItemSelectionChanged));
					_firstGallery.RemoveHandler(RibbonGalleryItem.UnselectedEvent, new RoutedEventHandler(OnGalleryItemSelectionChanged));
					_firstGallery.RemoveHandler(Keyboard.GotKeyboardFocusEvent, new KeyboardFocusChangedEventHandler(OnGalleryGotKeyboardFocus));
				}

				_firstGallery = value;

				if (_firstGallery != null)
				{
					_firstGallery.ShouldGalleryItemsAcquireFocus = false;

					_firstGallery.HighlightChanged += new EventHandler(OnGalleryHighlightChanged);
					_firstGallery.SelectionChanged += new RoutedPropertyChangedEventHandler<object>(OnGallerySelectionChanged);
					_firstGallery.AddHandler(RibbonGalleryItem.SelectedEvent, new RoutedEventHandler(OnGalleryItemSelectionChanged));
					_firstGallery.AddHandler(RibbonGalleryItem.UnselectedEvent, new RoutedEventHandler(OnGalleryItemSelectionChanged));
					_firstGallery.AddHandler(Keyboard.GotKeyboardFocusEvent, new KeyboardFocusChangedEventHandler(OnGalleryGotKeyboardFocus), true);
				}
				UpdateSelectionProperties();
			}
		}

		internal TextBox EditableTextBoxSite
		{
			get
			{
				return _editableTextBoxSite;
			}
			set
			{
				_editableTextBoxSite = value;
			}
		}

		private static DataTemplate StringContentTemplate
		{
			get { return s_StringTemplate; }
		}

		// Used to indicate that the Text Properties are changing
		// Don't reenter callbacks
		private bool UpdatingText
		{
			get { return _cacheValid[(int)CacheBits.UpdatingText]; }
			set { _cacheValid[(int)CacheBits.UpdatingText] = value; }
		}

		// Selected item is being updated; Don't reenter callbacks
		private bool UpdatingSelectedItem
		{
			get { return _cacheValid[(int)CacheBits.UpdatingSelectedItem]; }
			set { _cacheValid[(int)CacheBits.UpdatingSelectedItem] = value; }
		}

		// Says if the SelectedItem has been temporarily mutated as the text is being updated
		private bool IsSelectedItemCached
		{
			get { return _cacheValid[(int)CacheBits.IsSelectedItemCached]; }
			set { _cacheValid[(int)CacheBits.IsSelectedItemCached] = value; }
		}

		// Highlighted item is being updated; Don't reenter callbacks
		private bool UpdatingHighlightedItem
		{
			get { return _cacheValid[(int)CacheBits.UpdatingHighlightedItem]; }
			set { _cacheValid[(int)CacheBits.UpdatingHighlightedItem] = value; }
		}

		#endregion

		#region Private Data

		private const string EditableTextBoxTemplateName = "PART_EditableTextBox";

		private TextBox _editableTextBoxSite;
		private int _textBoxSelectionStart; // the location of selection before call to TextUpdated.
		private RibbonGallery _firstGallery;
		private WeakReference _firstGalleryItem;
		private BitVector32 _cacheValid = new BitVector32(0);   // Condense boolean bits
		private UIElement _clonedElement;
		private static DataTemplate s_StringTemplate;
		private object _selectedItem, _selectedValue, _cachedSelectedItem;
		private string _selectedValuePath;

		private enum CacheBits
		{
			IsMouseOverItemsHost = 0x01,
			HasMouseEnteredItemsHost = 0x02,
			IsContextMenuOpen = 0x04,
			UpdatingText = 0x08,
			UpdatingSelectedItem = 0x10,
			CanExecute = 0x20,
			IsSelectedItemCached = 0x40,
			UpdatingHighlightedItem = 0x80,
		}

		#endregion Private Data
	}
}
